package Simulator;

import java.util.LinkedList;
import java.util.List;

public class Processor {
	List<Block> blocksList;
	Settings settings;
	int id;
	final int MODIFY = 1;
	final int SHARED = 2;
	final int INVALID = 3;
	final int DIRECT_MAPPED = 0;
	final int SET_ASSOCIATIVE = 1;

	/**
	 * @param settings
	 * @param id
	 */
	public Processor(Settings settings, int id) {
		this.settings = settings;
		this.id = id;
		blocksList = new LinkedList<Block>();
		String padding = "";
		for (int k = 0; k < settings.getBlockSize(); k++)
			padding += "0";

		String tag = "";
		for (int k = 0; k < settings.getBitTag() / 4; k++)
			tag += "0";

		for (int i = 0; i < settings.getBlockNum(); i++) {
			blocksList.add(new Block(i, 3, tag, padding, false));
		}
	}

	public int getBlockState(String address) {
		int index = getBlockId(address);
		String tag = getTagFromAddress(address);
		if (tag.equals(blocksList.get(index).tag))
			return blocksList.get(index).state;
		else
			return INVALID;
	}

	public int getBlockId(String address) {
		if (settings.getCacheType() == DIRECT_MAPPED) {
			return getDirectMappedBlockId(address);
		} else {
			return getSetAssociativeBlockId(address);
		}
	}

	public Block getDirectMappedBlockFromId(String binStr) {
		String myBinStr = padAddress(binStr);
		return blocksList.get(getDirectMappedBlockId(myBinStr));
	}

	public Block getSetAssociativeBlockFromId(String binStr) {
		String myBinStr = padAddress(binStr);
		return blocksList.get(getSetAssociativeBlockId(myBinStr));
	}

	public void setDirectMappedBlock(String address, Block myBlock) {
		Block newBlock = new Block(myBlock);
		blocksList.set(getBlockId(address), newBlock);
	}

	public void setSetAssociativeBlock(String address, Block myBlock) {
		Block newBlock = new Block(myBlock);
		newBlock.used = true;
		int blockId;
		if (getSetAssociativeBlock(address) != null) {
			blockId = getSetAssociativeBlock(address).id;
		} else {
			blockId = getBlockId(address);
		}
		newBlock.id = blockId;
		blocksList.set(blockId, newBlock);
		changeUsed(blockId);
	}

	public Block getDirectMappedBlock(String address) {
		String tag = getTagFromAddress(address);
		for (Block b : blocksList) {

			if ((tag.equals(b.tag) && (b.state != INVALID))) {
				return b;
			}
		}
		return null;
	}

	public void changeUsed(int blockId) {
		if (blockId % 2 == 0) {
			blocksList.get(blockId).used = true;
			blocksList.get(blockId + 1).used = false;
		} else {
			blocksList.get(blockId).used = true;
			blocksList.get(blockId - 1).used = false;
		}
	}

	public Block getSetAssociativeBlock(String address) {
		String tag = getTagFromAddress(address);
		for (Block b : blocksList) {
			if ((tag.equals(b.tag) && (b.state != INVALID))) {
				return b;
			}
		}
		return null;
	}

	public int getDirectMappedBlockId(String address) {
		address = Integer.toBinaryString(Integer.parseInt(address, 16));
		address = padAddress(address);
		String index = address.substring(settings.getBitTag()
				- settings.getIndex(), settings.getBitTag());
		return Integer.parseInt(index, 2);
	}

	public int getSetAssociativeBlockId(String address) {
		address = Integer.toBinaryString(Integer.parseInt(address, 16));
		address = padAddress(address);
		String index = address.substring(settings.getBitTag()
				- settings.getIndex(), settings.getBitTag());
		int selectedBlock = Integer.parseInt(index, 2);
		int blockId = (selectedBlock * 2) % settings.getBlockNum();
		if (blocksList.get(blockId).state == INVALID)
			return blockId;
		if (blocksList.get(blockId + 1).state == INVALID)
			return blockId + 1;
		if (blocksList.get(blockId).used == false)
			return blockId;
		else
			return (blockId + 1);
	}

	public String getTagFromAddress(String address) {
		address = Integer.toBinaryString(Integer.parseInt(address, 16));
		address = padAddress(address);
		String tag = address.substring(0, settings.getBitTag());
		tag = Integer.toHexString(Integer.parseInt(tag, 2));
		
		return tag;
	}

	public String padAddress(String address) {
		// get a binary address and pad 0 to the left
		while (address.length() < settings.getBitAddress()) {
			address = "0" + address;
		}
		return address;
	}
}
